#!/bin/sh
# ----------------------------------------------------------------------------------------------------------
# Copyright (c) 2025 Huawei Technologies Co., Ltd.
# This program is free software, you can redistribute it and/or modify it under the terms and conditions of
# CANN Open Software License Agreement Version 2.0 (the "License").
# Please refer to the License for details. You may not use this file except in compliance with the License.
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
# INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
# See LICENSE in the root of the software repository for the full text of the License.
# ----------------------------------------------------------------------------------------------------------

# 公共函数库
# 总setenv文件权限
SETENV_MOD="550"
# 总setenv文件可写状态权限
SETENV_WRITEABLE_MOD="600"
# 默认特性配置
DEFAULT_FEATURE_PARAM="all n all"
#export PS4='+ ${FUNCNAME[0]:+${FUNCNAME[0]}():} ${BASH_SOURCE}:${LINENO}: '
#set -x
RESET_MOD="750"

# db.info文件格式如下
# ---
# CommonLib|atc,fwkacllib
# Compiler|atc,fwkacllib
# ---
# 使用"|"分隔模块名和包名列表，包名列表使用","分隔
# 文件中块名保持以升序排序
PKG_DB_INFO_RELPATH="var/ascend_package_db.info"
# 缓存mod文件名
STASH_MOD_PATH="stash_mod.txt"
# stash_mod文件权限
STASH_FILE_MOD="600"

# 写日志
log() {
    local cur_date_="$(date +"%Y-%m-%d %H:%M:%S")"
    local log_type_="${1}"
    local msg_="${2}"
    local log_format_="[Common] [${cur_date_}] [${log_type_}]: ${msg_}"
    if [ "${log_type_}" = "INFO" ]; then
        echo "${log_format_}"
    elif [ "${log_type_}" = "WARNING" ]; then
        echo "${log_format_}"
    elif [ "${log_type_}" = "ERROR" ]; then
        echo "${log_format_}"
    elif [ "${log_type_}" = "DEBUG" ]; then
        echo "${log_format_}" 1> /dev/null
    fi
}

# 返回列表长度
__length_list() {
    local list="$1"
    local var="$2"
    local list_item
    local cnt=0

    for list_item in ${list}; do
        cnt=$((cnt+1))
    done

    eval "${var}=\"${cnt}\""
}

# 获取列表索引值
__index_list() {
    local list="$1"
    shift
    local list_item
    local cnt=0

    if [ $# -eq 0 ]; then
        return 0
    fi

    for list_item in ${list}; do
        if [ ${1} -eq ${cnt} ]; then
            eval "${2}=\"${list_item}\""
            shift 2
            if [ $# -eq 0 ]; then
                return 0
            fi
        fi
        cnt=$((cnt+1))
    done

    return 0
}

# 从列表中移除一项
__remove_item_in_list() {
    local to_removed="$1"
    shift
    local list="$*"
    local list_item
    local new_list

    for list_item in ${list}; do
        if [ "${to_removed}" != "${list_item}" ]; then
            if [ "${new_list}" = "" ]; then
                new_list="${list_item}"
            else
                new_list="${new_list} ${list_item}"
            fi
        fi
    done
    echo "${new_list}"
}

# 元素是否在列表中
__item_in_list() {
    local _outvar="$1"
    local _item="$2"
    shift 2
    local _list_item
    local _matched="false"

    for _list_item in $*; do
        if [ "${_item}" = "${_list_item}" ]; then
            _matched="true"
            break
        fi
    done
    eval "${_outvar}=\"${_matched}\""
}

# 反转列表
__reverse_list() {
    local _outvar="$1"
    local _list="$2"
    local _new_list=""
    local _list_item

    for _list_item in ${_list}; do
        if [ "${_new_list}" = "" ]; then
            _new_list="${_list_item}"
        else
            _new_list="${_list_item} ${_new_list}"
        fi
    done
    eval "${_outvar}=\"${_new_list}\""
}

# 修改各文件及目录的属性
change_own() {
    local recursive="$3"
    local option=""
    local username="${USERNAME}"
    local usergroup="${USERGROUP}"
    if [ "$2" != "NA" ]; then
        if [ "${recursive}" = "true" ]; then
            option="-R"
        fi
        eval chown ${option} -h \"$2\" \"$1\"
        if [ $? -ne 0 ]; then
            log "ERROR" "$1 chown failed!"
            return 1
        fi
    fi
}

# 获取install_for_all文件权限
get_install_for_all_mod() {
    local _outvar="$1"
    local _mod="$2"
    local _new_mod _other_mod

    _new_mod="${_mod%?}"
    _other_mod="${_new_mod#${_new_mod%?}}"
    _other_mod="$(($_other_mod & 5))"  # other权限位移除写权限，仅支持普通用户运行

    eval "${_outvar}=\"${_new_mod}${_other_mod}\""
}

# 修改各文件及目录的权限
change_mod() {
    local mod="$2"
    local install_for_all="$3"
    local recursive="$4"
    local option="" new_mod
    # 对于软连接，可能目标文件还没有拷贝进来，导致无法修改mod，这里过滤掉软连接
    if [ -L "$1" ]; then
        return 0
    fi
    if [ "$2" != "NA" ]; then
        if [ "${recursive}" = "true" ]; then
            option="-R"
        fi
        # 如果设置了install_for_all，则安装时other权限跟group权限对齐
        if [ "${install_for_all}" = "y" ]; then
            get_install_for_all_mod new_mod "$mod"
            chmod ${option} "${new_mod}" "$1"
        else
            chmod ${option} "$2" "$1"
        fi
        if [ $? -ne 0 ]; then
            log "ERROR" "$1 chmod failed!"
            return 1
        fi
    fi
    return 0
}

# 获取文件权限
get_file_mod() {
    local _outvar="$1"
    local _options="" _ret
    shift

    while true; do
        case "$1" in
        -L|--dereference)
            _options="${_options} $1"
            shift
            ;;
        *)
            break
            ;;
        esac
    done

    local _path="$1"
    local _result

    _result="$(stat ${_options} -c %a "${_path}")"
    _ret="$?" && [ $_ret -ne 0 ] && return $_ret
    eval "${_outvar}=\"${_result}\""
}

# 检查路径是否为绝对路径
__check_abs_path() {
    local path="$1"

    if [ "${path#/}" != "${path}" ]; then
        is_abs_path="true"
    else
        is_abs_path="false"
    fi
}

__set_abs_path() {
    local install_path="$1"
    local path="$2"
    local varname="$3"
    local is_abs_path

    __check_abs_path "${path}"
    if [ "${is_abs_path}" != "true" ]; then
        eval "${varname}=\"${install_path}/${path}\""
    else
        eval "${varname}=\"${path}\""
    fi
}

# 创建目录
make_dir() {
    local path="$1"
    mkdir -p "${path}"
    if [ $? -ne 0 ]; then
        log "ERROR" "${path} mkdir failed!"
        exit 1
    fi
    return 0
}

# 检查install_path在docker_root之中
check_install_path_in_docker_root() {
    local install_path="$1"
    local docker_root="$2"

    echo "${install_path}" | grep "^${docker_root}" > /dev/null 2>&1
    if [ $? -ne 0 ]; then
        log "ERROR" "check install path ${install_path} in docker root ${docker_root} failed!"
        return 1
    fi
    return 0
}

# 移除路径右侧斜线(/)
rstrip_path() {
    local _outvar="$1"
    local _path="$2"

    _path="$(echo "${_path}" | sed "s/\/\+\$//g")"
    eval "${_outvar}=\"${_path}\""
}

# 包是否在tools目录下
is_package_under_tools() {
    local _outvar="$1"
    local _package="$2"
    local _result_iput="false"

    if [ "${_package}" = "aoe" ] || [ "${_package}" = "nca" ] || [ "${_package}" = "amct_acl" ] || [ "${_package}" = "ncs" ]; then
        _result_iput="true"
    fi

    eval "${_outvar}=\"${_result_iput}\""
}

# 获取包目录名
get_package_dir() {
    local _outvar="$1"
    local _package="$2"
    local _is_under_tools _result

    is_package_under_tools "_is_under_tools" "${_package}"
    if [ "${_is_under_tools}" = "true" ]; then
        _result="tools"
    else
        _result=""
    fi
    eval "${_outvar}=\"${_result}\""
}

# 获取包目录路径
get_package_dirpath() {
    local _outvar="$1"
    local _package="$2"
    local _is_under_tools _result

    is_package_under_tools "_is_under_tools" "${_package}"
    if [ "${_is_under_tools}" = "true" ]; then
        _result="tools/${_package}"
    else
        _result="${_package}"
    fi
    eval "${_outvar}=\"${_result}\""
}

# 获取包ascend_install.info路径
get_package_install_info() {
    local _outvar="$1"
    local _install_path="$2"
    local _version_dir="$3"
    local _package="$4"
    local _package_dirpath=""

    eval "${_outvar}=\"\""

    get_package_dirpath "_package_dirpath" "${_package}"

    eval "${_outvar}=\"${_install_path}/${_version_dir}/${_package_dirpath}/ascend_install.info\""
}

# 获取包version.info路径
get_package_version_info() {
    local _outvar="$1"
    local _install_path="$2"
    local _version_dir="$3"
    local _package="$4"
    local _package_dirpath=""

    eval "${_outvar}=\"\""

    get_package_dirpath "_package_dirpath" "${_package}"

    eval "${_outvar}=\"${_install_path}/${_version_dir}/${_package_dirpath}/version.info\""
}

# 获取包filelist.csv路径
get_package_filelist() {
    local _outvar="$1"
    local _install_path="$2"
    local _version_dir="$3"
    local _package="$4"
    local _package_dirpath=""

    eval "${_outvar}=\"\""

    get_package_dirpath "_package_dirpath" "${_package}"

    eval "${_outvar}=\"${_install_path}/${_version_dir}/${_package_dirpath}/script/filelist.csv\""
}

# 获取包install_common_parser.sh路径
get_package_install_common_parser() {
    local _outvar="$1"
    local _install_path="$2"
    local _version_dir="$3"
    local _package="$4"
    local _package_dirpath=""

    eval "${_outvar}=\"\""

    get_package_dirpath "_package_dirpath" "${_package}"

    eval "${_outvar}=\"${_install_path}/${_version_dir}/${_package_dirpath}/script/install_common_parser.sh\""
}

# 获取latest_manager的install_common_parser.sh路径
get_latest_manager_install_common_parser() {
    local _outvar="$1"
    local _install_path="$2"
    local _latest_dir="$3"

    eval "${_outvar}=\"${_install_path}/${_latest_dir}/var/manager/install_common_parser.sh\""
}

# 获取包script目录路径
get_package_script_dirpath() {
    local _outvar="$1"
    local _install_path="$2"
    local _version_dir="$3"
    local _package="$4"
    local _package_dirpath=""

    eval "${_outvar}=\"\""

    get_package_dirpath "_package_dirpath" "${_package}"

    eval "${_outvar}=\"${_install_path}/${_version_dir}/${_package_dirpath}/script\""
}

# 检查参数不为空
check_param_not_empty() {
    local name="$1"
    local error_msg="$2"
    local value

    eval "value=\"\${${name}}\""

    if [ "${value}" = "" ]; then
        comm_log "ERROR" "$2"
        return 1
    fi

    return 0
}

# 检查文件存在
check_file_exists() {
    local path="$1"
    local error_msg="$2"

    if [ ! -f "${path}" ]; then
        comm_log "ERROR" "$2"
        return 1
    fi

    return 0
}

# 检查返回值是否为0
check_ret_error() {
    local ret="$1"
    local msg="$2"

    if [ ${ret} -ne 0 ]; then
        comm_log "ERROR" "${msg}"
        return ${ret}
    fi

    return 0
}

# 检查返回值是否为0
check_ret_warning() {
    local ret="$1"
    local msg="$2"

    if [ ${ret} -ne 0 ]; then
        comm_log "WARNING" "${msg}"
        return ${ret}
    fi

    return 0
}

# 获取真实路径
get_realpath() {
    local _outvar="$1"
    local _path_gr="$2"

    _path_gr="$(readlink -f "${_path_gr}")"
    eval "${_outvar}=\"${_path_gr}\""
}

# 检查返回值是否为0
cleanup_if_error() {
    local ret="$1"
    local cleanup="$2"

    if [ ${ret} -ne 0 ]; then
        eval "${cleanup}"
        return ${ret}
    fi

    return 0
}

# 获取临时目录
get_tmp_root() {
    local _outvar="$1"
    local _result_gtr

    if [ -d "${HOME}" ]; then
        _result_gtr="${HOME}"
    elif [ $(id -u) -eq 0 ] && [ -d "/root" ]; then
        _result_gtr="/root"
    else
        _result_gtr="${PWD}"
    fi

    eval "${_outvar}=\"${_result_gtr}\""
    return 0
}

# 获取临时文件
get_tmp_file() {
    local _outvar="$1"
    local _filename="$2"
    local _tmp_file_gtf _result

    get_tmp_root "_tmp_file_gtf"

    _result=$(mktemp "$_tmp_file_gtf/${_filename}_XXXXXX")
    check_ret_warning "$?" "mktemp $_tmp_file_gtf/${_filename}_XXXXXX failed."
    ret="$?" && [ $ret -ne 0 ] && return $ret

    eval "${_outvar}=\"${_result}\""
    return 0
}

# 获取包架构
get_scene_arch() {
    local _outvar="$1"
    local _scene_filepath="$2"
    local _result

    _result="$(grep "^arch=" "${_scene_filepath}" | cut -d= -f2-)"
    eval "${_outvar}=\"${_result}\""
}

# 打包feature参数
pack_feature_param() {
    local _outvar="$1"
    local _feature_type="$2"
    local _feature_exclude_all="$3"
    local _chip="$4"

    eval "${_outvar}=\"${_feature_type} ${_feature_exclude_all} ${_chip}\""
}

# 解包feature参数
# 注意，调用解包时参数不可加引号
unpack_feature_param() {
    local _feature_type_var="$1"
    local _feature_exclude_all_var="$2"
    local _chip_var="$3"
    shift 3
    eval "${_feature_type_var}=\"${1}\""
    eval "${_feature_exclude_all_var}=\"${2}\""
    eval "${_chip_var}=\"${3}\""
}

# 展开version.info文件中参数
expand_version_file() {
    if [ "${VERSION_FILE}" = "" ]; then
        return 0
    fi
    get_version "VERSION" "${VERSION_FILE}"
    get_version_dir "VERSION_DIR" "${VERSION_FILE}"
}

# 提取第一项
extract_1st() {
    local _outvar="$1"
    eval "${_outvar}=\"$2\""
}

# 提取第二项
extract_2nd() {
    local _outvar="$1"
    eval "${_outvar}=\"$3\""
}

# 路径转为sed正则表达式
path_to_regex() {
    local _outvar="$1"
    local _path="$2"
    local _reslut_ptr="$(echo "${_path}" | sed "s#\/#\\\/#g")"

    eval "${_outvar}=\"${_reslut_ptr}\""
}

# 设置默认值
set_default() {
    local _outvar="$1"
    local _input="$2"
    local _default="$3"

    if [ "${_input}" = "" ]; then
        eval "${_outvar}=\"${_default}\""
    else
        eval "${_outvar}=\"${_input}\""
    fi
}

# 标准化feature参数
# 输入的feature参数中，如果有all字段（表示安装所有feature）
# 则将feature参数重置为all，在后续流程中安装所有feature
# 卸载流程中，feature与chip强制为all，保证卸载掉block中的所有文件
normalize_feature() {
    local _outvar="$1"
    local _feature_nf="$2"
    local _operation="$3"

    if [ "$_operation" = "uninstall" ]; then
        eval "${_outvar}=\"all\""
        return 0
    fi

    if [ "$_feature_nf" = "" ]; then
        eval "${_outvar}=\"all\""
        return 0
    fi

    case "${_feature_nf}" in
    all,*)
        _feature_nf="all"
        ;;
    *,all)
        _feature_nf="all"
        ;;
    *,all,*)
        _feature_nf="all"
        ;;
    esac

    eval "${_outvar}=\"${_feature_nf}\""
}

# 删除软连接
remove_softlink_icp() {
    local softlink="$1"

    if [ "${softlink}" = "" ]; then
        return 0
    fi

    if [ "${softlink}" != "NA" ] && [ -L "${softlink}" ]; then
        rm -f "${softlink}"
        if [ $? -ne 0 ]; then
            log "ERROR" "remove ${softlink} failed!"
            return 1
        fi
    fi
    return 0
}

#移除文件
remove_file() {
    local target="$1"
    local softlink="$2"
    local ret
    if [ -e "${target}" ] || [ -L "${target}" ]; then
        rm -f "${target}"
        if [ $? -ne 0 ]; then
            log "ERROR" "remove ${target} failed!"
            return 1
        fi
    fi
    remove_softlink_icp "${softlink}"
    ret="$?" && [ ${ret} -ne 0 ] && return ${ret}

    return 0
}

# 创建文件夹
create_folder() {
    local install_path="$1"
    local target="$2"
    local softlinks_str="$3"
    local ret target_abs

    __set_abs_path "${install_path}" "${target}" "target_abs"

    if [ ! -d "${target_abs}" ]; then
        make_dir "${target_abs}"
        ret="$?" && [ ${ret} -ne 0 ] && return ${ret}
    fi
    change_mod "${target_abs}" "${RESET_MOD}" ""
    ret="$?" && [ ${ret} -ne 0 ] && return ${ret}
    if [ "${softlinks_str}" != "NA" ]; then
        create_softlink_by_install_path "${install_path}" "${target}" "${softlinks_str}"
        ret="$?" && [ ${ret} -ne 0 ] && return ${ret}
    fi
    return 0
}

# 创建目录
create_dirs() {
    local install_path="$1"
    local line="$2"
    local target
    local target_abs
    local softlinks_str
    local ret

    __index_list "${line}" 1 "target" 4 "softlinks_str"

    __set_abs_path "${install_path}" "${target}" "target_abs"

    if [ -L "${target_abs}" ] ; then
        rm -f "${target_abs}"
        log "WARNING" "${target_abs} is an existing soft-link, deleted."
    fi
    create_folder "${install_path}" "${target}" "${softlinks_str}"
    ret="$?" && [ ${ret} -ne 0 ] && return ${ret}
    return 0
}

# 修改目录的权限和属组
reset_mod_dirs() {
    local install_path="$1"
    local line="$2"
    local mod
    local target
    local target_abs
    local is_abs_path ret

    __index_list "${line}" 1 "target"

    __set_abs_path "${install_path}" "${target}" "target_abs"

    # 目录不存在时跳过
    if [ ! -d "${target_abs}" ]; then
        return 0
    fi
    # 只处理目录，没有处理目录的软链接
    change_mod "${target_abs}" "${RESET_MOD}" "" "false"
    ret="$?" && [ ${ret} -ne 0 ] && return ${ret}

    return 0
}

# 递归修改目录的权限和属组
reset_mod_dirs_recursive() {
    local install_path="$1"
    local line="$2"
    local mod
    local target
    local target_abs
    local is_abs_path ret

    __index_list "${line}" 1 "target"

    __set_abs_path "${install_path}" "${target}" "target_abs"

    # 目录不存在时跳过
    if [ ! -d "${target_abs}" ]; then
        return 0
    fi
    # 只处理目录，没有处理目录的软链接
    change_mod "${target_abs}" "${RESET_MOD}" "" "true"
    ret="$?" && [ ${ret} -ne 0 ] && return ${ret}

    return 0
}

__unpack_softlinks() {
    local softlinks_str="$1"
    local varname="$2"
    local item
    local temp
    OLD_IFS="${IFS}"
    IFS=";"
    temp=""
    for item in ${softlinks_str}; do
        if [ "${temp}" = "" ]; then
            temp="${item}"
        else
            temp="${temp} ${item}"
        fi
    done
    IFS="${OLD_IFS}"

    eval "${varname}=\"${temp}\""
}

#移除文件夹
remove_dir_icp() {
    if [ -e "$1" ] || [ -L "$1" ]; then
        rm -fr "$1"
        if [ $? -ne 0 ]; then
            log "ERROR" "$1 remove failed!"
            return 1
        fi
    fi
    return 0
}

# 创建软链时，移除存在的目录
remove_exists_dir_in_create_softlink() {
    local dirpath="$1"

    if [ -d "${dirpath}" ] && [ ! -L "${dirpath}" ]; then
        log "WARNING" "${dirpath} is an existing directory in create softlink, deleted."
        change_mod "${dirpath}" "700" "n" "true"
        remove_dir_icp "${dirpath}"
    fi
}

# 创建绝对软链接
create_softlink_icp_absolute() {
    local src_path="$1"
    local dst_path="$2"

    remove_exists_dir_in_create_softlink "${dst_path}"
    ln -sfn "${src_path}" "${dst_path}"
    if [ $? -ne 0 ]; then
        log "ERROR" "create softlink absolute from ${src_path} to ${dst_path} failed!"
        return 1
    fi
    return 0
}

# 该函数与common_func.inc脚本中的相同
# install_common_parser.sh不一定能source到common_func.inc（原因见source common_func.inc的注释）
# 所以这里需要重复定义
create_softlink_icp_relative() {
    local src_path="$1"
    local dst_path="$2"
    local source="top${src_path}"
    local target="top${dst_path}"

    # 若变量内容从尾向前的数据符合，则将符合的最短数据删除
    local common="${target%/*}"
    # 若变量内容从头开始的数据符合，则将符合的最短数据删除
    local forward="${source#"$common"/}"

    local result=""

    while [ "${forward}" = "${source}" ]; do
        common="$(dirname "$common")"
        forward="${source#"$common"/}"
        result="../${result}"
    done

    result="${result}${forward}"

    remove_exists_dir_in_create_softlink "${dst_path}"
    ln -sfn "${result}" "${dst_path}"
    if [ $? -ne 0 ]; then
        log "ERROR" "create softlink relative from ${src_path} to ${dst_path} failed!"
        return 1
    fi
    return 0
}

# 创建软连接
create_softlink_by_install_path() {
    local install_path="$1"
    local target="$2"
    local softlinks_str="$3"
    local softlinks softlink
    local target_abs
    local softlink_abs
    local is_abs_path
    local ret

    if [ "${softlinks_str}" = "NA" ]; then
        return 0
    fi

    __unpack_softlinks "${softlinks_str}" "softlinks"

    for softlink in ${softlinks}; do
        __check_abs_path "${target}"
        if [ "${is_abs_path}" = "true" ]; then
            __set_abs_path "${install_path}" "${softlink}" "softlink_abs"
            create_softlink_icp_absolute "${target}" "${softlink_abs}"
            ret="$?" && [ ${ret} -ne 0 ] && return ${ret}
            continue
        fi

        __check_abs_path "${softlink}"
        if [ "${is_abs_path}" = "true" ]; then
            __set_abs_path "${install_path}" "${target}" "target_abs"
            create_softlink_icp_absolute "${target_abs}" "${softlink}"
            ret="$?" && [ ${ret} -ne 0 ] && return ${ret}
            continue
        fi

        create_softlink_icp_relative "${install_path}/${target}" "${install_path}/${softlink}"
        ret="$?" && [ ${ret} -ne 0 ] && return ${ret}
    done

    return 0
}

# 创建软链接。icp后缀为避免重名
create_softlink_icp() {
    local options=""
    local is_relative="false"

    while true; do
        case "$1" in
        -r|--relative)
            is_relative="true"
            shift
            ;;
        -*)
            log "ERROR" "unsupported option $1 in create softlink icp!"
            return 1
            ;;
        *)
            break
            ;;
        esac
    done

    local src_path="$1"
    local dst_path="$2"

    if [ "${is_relative}" = "true" ]; then
        create_softlink_icp_relative "${src_path}" "${dst_path}"
    else
        create_softlink_icp_absolute "${src_path}" "${dst_path}"
    fi
}

# 修改权限和属组
change_mod_and_own(){
    local target="$1"
    local mod="$2"
    local own="$3"
    local install_for_all="$4"
    local recursive="$5"
    local ret

    change_mod "${target}" "${mod}" "${install_for_all}" "${recursive}"
    ret="$?" && [ $ret -ne 0 ] && return $ret

    change_own "${target}" "${own}" "${recursive}"
    ret="$?" && [ $ret -ne 0 ] && return $ret

    return 0
}

# 修改目录的权限和属组
change_mod_and_own_dirs() {
    local install_path="$1"
    local line="$2"
    local target
    local mod
    local own
    local is_abs_path

    __index_list "${line}" 1 "target" 2 "mod" 3 "own"

    __check_abs_path "${target}"
    if [ "${is_abs_path}" != "true" ]; then
        target="${install_path}/${target}"
    fi
    if [ ! -d "${target}" ]; then
        return 0
    fi
    # 只处理目录，没有处理目录的软链接
    change_mod_and_own "${target}" "${mod}" "${own}" "${INSTALL_FOR_ALL}" "false"
}

# 创建stash_mod.txt文件
create_stash_mod() {
    local install_path="$1"
    rm -f "${install_path}/${STASH_MOD_PATH}"
    touch "${install_path}/${STASH_MOD_PATH}"
    chmod ${STASH_FILE_MOD} "${install_path}/${STASH_MOD_PATH}"
}

# 删除stash_mod.txt文件
remove_stash_mod() {
    local install_path="$1"
    rm -f "${install_path}/${STASH_MOD_PATH}"
}

# 修改目录的权限和属组
restore_stash_mod() {
    local install_path="$1"
    local line="$2"
    local target
    local target_abs
    local mod
    local is_abs_path ret

    __index_list "${line}" 0 "target" 1 "mod"

    __set_abs_path "${install_path}" "${target}" "target_abs"

    # 目录不存在时跳过
    if [ ! -d "${target_abs}" ]; then
        return 0
    fi
    # 只处理目录，没有处理目录的软链接
    change_mod "${target_abs}" "${mod}" "${INSTALL_FOR_ALL}"
    ret="$?" && [ ${ret} -ne 0 ] && return ${ret}

    return 0
}

# 修改目录的权限，暂存原文件权限
reset_mod_dirs_with_stash_mod() {
    local install_path="$1"
    local line="$2"
    local mod
    local target
    local target_abs
    local is_abs_path ret

    __index_list "${line}" 1 "target"

    __set_abs_path "${install_path}" "${target}" "target_abs"

    # 目录不存在时跳过
    if [ ! -d "${target_abs}" ]; then
        return 0
    fi

    get_file_mod "mod" "${target_abs}"
    echo "${target}:${mod}" >> "${install_path}/${STASH_MOD_PATH}"

    # 只处理目录，没有处理目录的软链接
    change_mod "${target_abs}" "${RESET_MOD}" ""
    ret="$?" && [ ${ret} -ne 0 ] && return ${ret}

    return 0
}

# 删除软连接列表
remove_softlinks() {
    local install_path="$1"
    local softlinks_str="$2"
    local softlinks softlink softlink_abs ret

    __unpack_softlinks "${softlinks_str}" "softlinks"

    for softlink in ${softlinks}; do
        __set_abs_path "${install_path}" "${softlink}" "softlink_abs"
        remove_softlink_icp "${softlink_abs}"
        ret="$?" && [ ${ret} -ne 0 ] && return ${ret}
    done
    return 0
}

# 删除安装文件夹
remove_install_dirs() {
    local install_path="$1"
    local line="$2"
    local target softlinks_str is_abs_path

    __index_list "${line}" 1 "target" 4 "softlinks_str"

    if [ "${target}" != "NA" ]; then
        __check_abs_path "${target}"
        if [ "${is_abs_path}" != "true" ]; then
            target="${install_path}/${target}"
        fi
        if [ -d "${target}" ]; then
            # 不同的blocks中，可能配置相同的dir_info。目录不为空时不删除。
            if [ "$(ls -A "${target}")" != "" ]; then
                return 0
            fi
            remove_dir_icp "${target}"
            ret="$?" && [ $ret -ne 0 ] && return $ret
        fi

        if [ "${softlinks_str}" != "NA" ]; then
            remove_softlinks "${install_path}" "${softlinks_str}"
            ret="$?" && [ ${ret} -ne 0 ] && return ${ret}
        fi
    fi
    return 0
}

# 获取所有的blocks
get_blocks_info() {
    local _outvar="$1"
    local _install_path="$2"
    local _result _db_filepath="${_install_path}/${PKG_DB_INFO_RELPATH}"

    if [ ! -f "${_db_filepath}" ]; then
        return 0
    fi
    _result="$(cut -d'|' -f1 "${_db_filepath}" | xargs)"
    eval "${_outvar}=\"${_result}\""
}

# 列表转为正则表达式
trans_list_to_regex() {
    local _outvar="$1"
    local _value="$2"
    # 移除右侧空白符，mawk无法编译右侧存在空白符时的正则表达式，如：^(EngineeringCommon|)$
    local _result="$(echo "$_value" | sed 's/\s\+$//g' | sed 's/ /|/g' | xargs printf "^(%s)$")"
    eval "${_outvar}=\"${_result}\""
}

parse_filelist_core() {
    awk -F, '{ print $3,$4,$6,$7,$9,$12,$13,$14,$15 }'
}

# 过滤公共路径条目
filter_common_dirs() {
    awk -F, '
        $11 == "Y" {
            print $0
        }
        $11 == "YY" {
            print $1 "," $2 "," $3 "," $4 "," $5 "," $6 "," $7 "," $8 ",NA," $10 "," $11 "," $12 "," $13 "," $14 "," $15 "," $16
        }
    '
}

# 过滤包内软链
filter_pkg_inner_softlink() {
    awk -F, '$15 != "NA" { print $0 }'
}

# 过滤块
filter_blocks() {
    local blocks="$1"
    local blocks_reg

    trans_list_to_regex "blocks_reg" "$blocks"
    awk -F, "\$14 ~ \"$blocks_reg\"  {print \$0}"
}

# 过滤不匹配块
filterfalse_blocks() {
    local blocks="$1"
    local blocks_reg

    trans_list_to_regex "blocks_reg" "$blocks"
    awk -F, "\$14 !~ \"$blocks_reg\"  {print \$0}"
}

# 过滤操作类型条目
filter_operate_type() {
    local operator_type="$1"
    local operator_type_reg

    trans_list_to_regex "operator_type_reg" "$operator_type"

    # ~为部分匹配，正则表达式需要匹配头尾
    awk -F, "\$2 ~ \"$operator_type_reg\"  {print \$0}"
}

# 过滤安装类型条目
filter_install_type() {
    local install_type="$1"
    local install_type_reg

    # 注意：特征串不能嵌套，否则会错误匹配
    if [ "$install_type" != "full" ] && [ "$install_type" != "debug" ]; then
        install_type_reg="(all)|($install_type)"
    else
        install_type_reg="(all)|(docker)|(devel)|(run)"
    fi

    awk -F, "\$8 ~ \"$install_type_reg\"  {print \$0}"
}

# 过滤特性参数
filter_feature_param() {
    local feature_param="$1"
    local feature_type
    local feature_exclude_all  # feature是否为排除公共all文件
    local chip chip_list  # 芯片类型
    local feature_list feature_type_list

    unpack_feature_param "feature_type" "feature_exclude_all" "chip" ${feature_param}

    if [ "$feature_type" != "all" ]; then
        feature_list="$(echo $feature_type | tr ',' ' ')"
        if [ "${feature_exclude_all}" = "y" ]; then
            feature_type_list="${feature_list}"
        else
            feature_type_list="comm ${feature_list}"
        fi
    else
        feature_type_list="all"
    fi

    if [ "$chip" != "all" ]; then
        chip_list="all $(echo $chip | tr ',' ' ')"
    else
        chip_list="all"
    fi

    # filelist中的feature为all，表示所有场景下都安装这个文件
    # 输入参数中的feature为all，表示安装所有的feature
    awk -v feature_type_list="${feature_type_list}" \
        -v chip_list="${chip_list}" '
        BEGIN {
            FS= ","
            split(feature_type_list, input_feature_type_arr, " ")
            split(chip_list, input_chip_arr, " ")
        }

        function match_feature_type(features_str) {
            if (feature_type_list == "all") {
                return 1
            }
            matched_feature_type_tmp=0
            split(features_str, features, ";")
            for(i in features)
            {
                for(j in input_feature_type_arr) {
                    if(input_feature_type_arr[j] == features[i]) {
                        matched_feature_type_tmp = 1
                        break;
                    }
                }
            }
            return matched_feature_type_tmp
        }

        function match_chip(chip_str) {
            if (chip_list == "all") {
                return 1
            }
            matched_chip_tmp=0
            split(chip_str, chips, ";")
            for(i in chips)
            {
                for(j in input_chip_arr) {
                    if(input_chip_arr[j] == chips[i]) {
                        matched_chip_tmp = 1
                        break;
                    }
                }
            }
            return matched_chip_tmp
        }

        {
            matched_feature_type = match_feature_type($10);
            if (matched_feature_type == 0) next;

            matched_chip = match_chip($16)
            if (matched_chip == 0) next

            print $0
        }'
}

# 读取fileist的第2行到最后行
tail_filelist() {
    local filelist_path="$1"
    tail -n +2 "$filelist_path"
}

# 解析filelist.csv脚本
parse_filelist() {
    local install_type="$1"
    local operate_type="$2"
    local filelist_path="$3"
    local feature_param="$4"
    local filter_type="$5"
    local blocks="$6"

    if [ ! -f "$filelist_path" ]; then
        log "ERROR" "filelist $filelist_path does not exist!"
        exit 1
    fi

    # 注意：这里的filelist需要是全局变量。其它函数（如add_filelist_blocks_info）会引用这个变量。
    filelist=$(
        parse_filelist_v2 "$install_type" "$operate_type" "$filelist_path" "$feature_param" \
            "$filter_type" "$blocks"
    )
}

# 解析filelist.csv脚本
parse_filelist_v2() {
    local install_type="$1"
    local operate_type="$2"
    local filelist_path="$3"
    local feature_param="$4"
    local filter_type="$5"
    local blocks="$6"
    local filter_cmds=""

    if [ "$operate_type" != "all" ]; then
        filter_cmds="$filter_cmds | filter_operate_type \"$operate_type\""
    fi
    if printf "%s" "$filter_type" | grep -Eq "\<filter_common_dirs\>"; then
        filter_cmds="$filter_cmds | filter_common_dirs"
    fi
    if printf "%s" "$filter_type" | grep -Eq "\<filter_by_pkg_inner_softlink\>"; then
        filter_cmds="$filter_cmds | filter_pkg_inner_softlink"
    fi
    if printf "%s" "$filter_type" | grep -Eq "\<filter_by_blocks\>|\<filterfalse_blocks\>" && [ "$blocks" != "" ]; then
        filter_cmds="$filter_cmds | filterfalse_blocks \"$blocks\""
    fi
    if printf "%s" "$filter_type" | grep -Eq "\<filter_blocks\>" && [ "$blocks" != "" ]; then
        filter_cmds="$filter_cmds | filter_blocks \"$blocks\""
    fi

    tail_filelist "$filelist_path" \
    | filter_install_type "$install_type" \
    | filter_feature_param "$feature_param" \
    | eval cat $filter_cmds \
    | parse_filelist_core
}


pack_exec_params() {
    local _outvar="$1"
    local _exec_mode="$2"

    eval "${_outvar}=\"${_exec_mode}\""
}


# 迭代filelist中的条目，执行操作
foreach_filelist_exec() {
    local filelist="$1"
    local sort_filelist="$2"
    local exec_mode="$3"
    local exec_func="$4"
    local install_path="$5"
    local ret=0 tmp_ret exec_params
    shift 5

    pack_exec_params "exec_params" "${exec_mode}"

    if [ "${sort_filelist}" = "reverse" ]; then
        # 对第二列文件路径做倒序排列，保证先删除子文件夹，再删除父文件夹
        # 不可以使用echo "${filelist}"
        # 在dash中会消耗\\$username:\\$usergroup中的一个反斜线（bash与busybox的sh无此问题）
        # 需要使用here document，防止shell的解析过程
        filelist=$(sort -k2,2 -b -r <<EOF
${filelist}
EOF
        )
    elif [ "${sort_filelist}" = "no" ]; then
        # 不排序
        :
    else
        log "ERROR" "sort_filelist param wrong! sort_filelist is ${sort_filelist}, exec_func is ${exec_func}, parse_func is ${parse_func}"
        exit 1
    fi

    if [ "${exec_mode}" = "concurrency" ] || [ "${exec_mode}" = "normal" ]; then
        # 并发模式或正常模式
        :
    else
        log "ERROR" "exec_mode param wrong! exec_mode is ${exec_mode}, exec_func is ${exec_func}, parse_func is ${parse_func}"
        exit 1
    fi

    while read line; do
        array=${line}
        __length_list "${array}" "len_array"
        if [ ${len_array} -eq 0 ]; then
            continue
        fi
        "${exec_func}" "${install_path}" "${line}" "${exec_params}" "$@"
        tmp_ret="$?" && [ ${tmp_ret} -ne 0 ] && ret="${tmp_ret}"
    done << EOF
${filelist}
EOF

    if [ "${exec_mode}" = "concurrency" ]; then
        wait
    fi

    return $ret
}

# 迭代遍历filelist中的条目，执行一些操作
foreach_filelist() {
    local filter_type="$1"
    local exec_func="$2"
    local install_type="$3"
    local install_path="$4"
    local operate_type="$5"
    local filelist_path="$6"
    local feature_param="$7"
    local sort_filelist="$8"  # 排序filelist，可选参数为[no,reverse]
    local exec_mode="$9"  # 执行模式，可选参数为[normal,concurrency]
    shift 9
    local ret blocks

    if [ "$filter_type" = "filter_by_blocks" ]; then
        get_blocks_info "blocks" "$install_path"
    fi

    foreach_filelist_v2 "$exec_func" "$install_type" "$install_path" "$operate_type" "$filelist_path" \
        "$feature_param" "$filter_type" "$blocks" "$sort_filelist" "$exec_mode" "$@"
    ret="$?" && [ $ret -ne 0 ] && return $ret

    return 0
}

# 迭代遍历filelist中的条目，执行一些操作
foreach_filelist_v2() {
    local exec_func="$1"
    local install_type="$2"
    local install_path="$3"
    local operate_type="$4"
    local filelist_path="$5"
    local feature_param="$6"
    local filter_type="$7"
    local blocks="$8"
    local sort_filelist="$9"  # 排序filelist，可选参数为[no,reverse]
    local exec_mode="${10}"  # 执行模式，可选参数为[normal,concurrency]
    shift 10
    local filter_cmds=""
    local ret

    parse_filelist "${install_type}" "${operate_type}" "${filelist_path}" "${feature_param}" \
        "${filter_type}" "${blocks}"

    foreach_filelist_exec "${filelist}" "${sort_filelist}" "${exec_mode}" "${exec_func}" "${install_path}" "$@"
    ret="$?" && [ $ret -ne 0 ] && return $ret

    return 0
}

# 解析stash_mod.txt
parse_stashmod() {
    local stashmod_path="$1"

    if [ ! -f "${stashmod_path}" ]; then
        log "ERROR" "stash mod ${stashmod_path} does not exist!"
        exit 1
    fi

    stashmod_list=$(awk '
        BEGIN{
            FS=":"
        }
        {
            print $1,$2
        }' "${stashmod_path}")
}

# 迭代遍历stash_mod中的条目，执行操作
foreach_stashmod() {
    local exec_func="$1"
    local install_path="$2"
    local sort_type="$3"  # 排序方式，可选参数为[no,reverse]
    local array
    local len_array
    local line
    local ret=0 tmp_ret

    parse_stashmod "${install_path}/${STASH_MOD_PATH}"

    if [ "${sort_type}" = "reverse" ]; then
        # 对第二列文件路径做倒序排列，保证先处理子文件夹，再处理父文件夹
        stashmod_list=$(echo "${stashmod_list}" | sort -k1,1 -r)
    elif [ "${sort_type}" = "no" ]; then
        # 不排序
        echo > /dev/null
    else
        log "ERROR" "sort_type param wrong! sort_type is ${sort_type}, exec_func is ${exec_func}"
        exit 1
    fi

    while read line; do
        array=${line}
        __length_list "${array}" "len_array"
        if [ ${len_array} -eq 0 ]; then
            continue
        fi
        "${exec_func}" "${install_path}" "${line}"
        tmp_ret="$?" && [ ${tmp_ret} -ne 0 ] && ret="${tmp_ret}"
    done << EOF
${stashmod_list}
EOF

    return $ret
}

# 创建目录并且设置权限
make_dir_with_permission() {
    local path="$1"
    local mod="$2"
    local username="$3"
    local usergroup="$4"
    local install_for_all="$5"
    local ret

    make_dir "${path}"
    ret="$?" && [ ${ret} -ne 0 ] && return ${ret}

    change_mod "${path}" "${mod}" "${install_for_all}"
    ret="$?" && [ ${ret} -ne 0 ] && return ${ret}

    change_own "${path}" "${username}:${usergroup}"
    ret="$?" && [ ${ret} -ne 0 ] && return ${ret}

    return 0
}

# blocks转换为db.info配置
blocks_to_db_item() {
    local package="$1"
    local version_dir="$2"

    xargs printf "%s|[$package:$version_dir]\n"
}

# 折叠第2个参数
# 需要保证输入数据第1个参数已排序
fold_2() {
    local fs="$1"
    local concat="$2"
    local options=""

    if [ "$fs" != "" ]; then
        options="-F$fs"
    fi

    awk $options -v CONCAT="$concat" '
    NR == 1 {
        field1 = $1
        field2 = $2
    }
    NR != 1 {
        if (field1 == $1) {
            field2 = field2 CONCAT $2
        } else {
            print field1 FS field2
            field1 = $1
            field2 = $2
        }
    }
    END {
        if (field1) {
            print field1 FS field2
        }
    }'
}

# 折叠第3个参数，保持第2个参数
# 需要保证输入数据第1个参数已排序
fold_3_keep_2() {
    local fs="$1"
    local concat="$2"
    local options=""

    if [ "$fs" != "" ]; then
        options="-F$fs"
    fi

    awk $options -v CONCAT="$concat" '
    NR == 1 {
        field1 = $1
        field2 = $2
        field3 = $3
    }
    NR != 1 {
        if (field1 == $1) {
            field3 = field3 CONCAT $3
        } else {
            print field1 FS field2 FS field3
            field1 = $1
            field2 = $2
            field3 = $3
        }
    }
    END {
        if (field1) {
            print field1 FS field2 FS field3
        }
    }'
}

# 根据第1列排序
sort_1() {
    local sep="$1"
    local options=""
    if [ "$sep" != "" ]; then
        options="-t$sep"
    fi
    sort $options -k1,1 -s
}

# 显示3,4列有差异的条目
show_diff_3_4() {
    awk '$3 != $4 { print $0 }'
}

# 显示具有最少列数的条目
show_min_nf() {
    local min_nf="$1"
    awk -v MIN_NF="$min_nf" 'NF >= MIN_NF { print $0 }'
}

# 选取第3,2,1列
select_fields_3_2_1() {
    awk '{print $3, $2, $1}'
}

# 选取第1列
select_fields_1() {
    awk '{print $1}'
}

# 删除db.info配置
del_db_items() {
    local package="$1"
    local version_dir="$2"

    sed "s/\\[$package:$version_dir\\]//g; /|\$/d"
}

# 保留块最后一个配置
# 输出：第1列（块），第2列（包名），第3列（版本目录）
remain_db_last_item() {
    awk -F '[|:\\[\\]]+' '{ print $1, $(NF-2), $(NF-1) }'
}

# 移除空白行
remove_blank_line() {
    sed '/^$/d'
}

# 保证文件权限
ensure_permission() {
    local path="$1"
    local mod="$2"
    local username="$3"
    local usergroup="$4"
    local install_for_all="$5"

    change_mod "$path" "$mod" "$install_for_all"
    ret=$? && [ $ret -ne 0 ] && return $ret

    change_own "$path" "$username:$usergroup"
    ret=$? && [ $ret -ne 0 ] && return $ret

    return 0
}

# 创建文件
touch_file() {
    local path="$1"
    local mod="$2"
    local username="$3"
    local usergroup="$4"
    local install_for_all="$5"
    local ret

    touch "$path"
    ret=$? && [ $ret -ne 0 ] && return $ret

    ensure_permission "$path" "$mod" "$username" "$usergroup" "$install_for_all"
    ret=$? && [ $ret -ne 0 ] && return $ret

    return 0
}

# 保证文件存在
ensure_file() {
    local path="$1"
    local mod="$2"
    local username="$3"
    local usergroup="$4"
    local install_for_all="$5"
    local ret

    if [ ! -f "$path" ]; then
        touch_file "$path" "$mod" "$username" "$usergroup" "$install_for_all"
        ret=$? && [ $ret -ne 0 ] && return $ret
    fi

    return 0
}

# 写文本文件
write_text() {
    local content="$1"
    local filepath="$2"

    printf "%s\n" "$content" > "$filepath"
}

# 修改文件权限并操作
with_chmod() {
    local path="$1"
    local mod="$2"
    local origin_mod ret
    shift 2

    origin_mod="$(stat -L -c "%a" "$path")"
    chmod "$mod" "$path"
    "$@"
    ret="$?"
    chmod "$origin_mod" "$path"

    return $ret
}

# filelist中所有公共目录的块
all_common_dirs_blocks_in_filelist() {
    local install_type="$1"
    local filelist_path="$2"
    local feature_param="$3"

    parse_filelist_v2 "$install_type" "all" "$filelist_path" "$feature_param" "filter_common_dirs" "" \
        | cut -d' ' -f8 | sort | uniq
}
